package cn.com.joycode.nimble.opencv;


import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.Loader;
import org.bytedeco.javacv.CanvasFrame;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.global.opencv_imgproc;
import org.bytedeco.opencv.opencv_core.Point;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_java;
import org.opencv.core.CvType;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.InputStream;
import java.nio.ByteBuffer;

import static org.bytedeco.opencv.global.opencv_imgproc.TM_CCOEFF_NORMED;

@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JavaCvUtil {

    static {
        Loader.load(opencv_java.class);
        //Loader.load(opencv_core.class);
    }

    public static Mat imread(String filePath) {
        try {
            BufferedImage image = ImageIO.read(new File(filePath));
            return imread(image);
        } catch (
                Exception e) {
            log.error("图像文件加载失败", e);
            return new Mat();
        }
    }

    public static Mat imread(BufferedImage image) {
        try {
            Java2DFrameConverter java2dFrameConverter = new Java2DFrameConverter();
            Frame frame = java2dFrameConverter.convert(image);
            OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
            return converter.convert(frame).clone();
        } catch (Exception e) {
            log.error("图像文件加载失败", e);
            return new Mat();
        }
    }

    public static Mat imread(InputStream is) {
        try {
            BufferedImage image = ImageIO.read(is);
            Java2DFrameConverter java2dFrameConverter = new Java2DFrameConverter();
            Frame frame = java2dFrameConverter.convert(image);
            OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat();
            return converter.convert(frame).clone();
        } catch (Exception e) {
            return new Mat();
        }
    }

    public static boolean imwrite(Mat mat, String filePath) {
        try {
            OpenCVFrameConverter.ToMat convert = new OpenCVFrameConverter.ToMat();
            Frame frame = convert.convert(mat);
            Java2DFrameConverter java2dFrameConverter = new Java2DFrameConverter();
            BufferedImage bufferedImage = java2dFrameConverter.convert(frame);
            ImageIO.write(bufferedImage, "PNG", new File(filePath));
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    public static void show(String title, Mat mat) {
        try {
            OpenCVFrameConverter.ToMat openCvConverter = new OpenCVFrameConverter.ToMat();
            CanvasFrame canvas = new CanvasFrame(title, 1);
            if (mat.size().height() > 960) {
                canvas.setCanvasScale(0.5);
            }
            canvas.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            canvas.showImage(openCvConverter.convert(mat));
            canvas.waitKey();
        } catch (Exception e) {
        }
    }

    public static Mat grayScale(Mat mat) {
        Mat result = new Mat(mat.dims(), mat.size(), CvType.CV_8UC1);
        opencv_imgproc.cvtColor(mat, result, opencv_imgproc.CV_BGRA2GRAY);
        return result;
    }

    public static boolean matchTemplate(Mat target, Mat template, double threshold) {
        //log.info("灰度化处理开始:{}x{},{}x{}", target.size().width(), target.size().height(), template.size().width(), template.size().height());
        Mat target1 = JavaCvUtil.grayScale(target);
        //log.info("目标灰度化处理结束");
        Mat template1 = JavaCvUtil.grayScale(template);
        //log.info("模板灰度化处理结束");
        Mat result = new Mat(template1.dims(), template1.size(), opencv_core.CV_8UC1);
        //log.info("处理结果创建完成");
        opencv_imgproc.matchTemplate(target1, template1, result, TM_CCOEFF_NORMED);
        //log.info("匹配结束");
        DoublePointer minVal = new DoublePointer(1);
        DoublePointer maxVal = new DoublePointer(1);
        Point minPoint = new Point();
        Point maxPoint = new Point();
        //log.info("提取结果");
        opencv_core.minMaxLoc(result, minVal, maxVal, minPoint, maxPoint, null);
        log.info("min={} -> ({},{}),max={} -> ({},{})", minVal.get(), minPoint.x(), minPoint.y(), maxVal.get(), maxPoint.x(), maxPoint.y());
        if (maxVal.get() < threshold) {
            log.info("没有匹配");
            return false;
        }
        return true;
    }


    public static MatchResult matchTemplate(Mat target, Mat template, int left, int top, int width, int height, double threshold) {
        MatchResult result = new MatchResult();
        try {
            Mat sub = new Mat(width, height, target.type());
            Mat tmp = new Mat(target, new Rect(left, top, width, height));
            tmp.copyTo(sub);
            Mat target1 = grayScale(sub);
            Mat template1 = grayScale(template);
            Mat ret = new Mat(template.dims(), template.size(), opencv_core.CV_8UC1);
            opencv_imgproc.matchTemplate(target1, template1, ret, TM_CCOEFF_NORMED);
            DoublePointer minVal = new DoublePointer(1);
            DoublePointer maxVal = new DoublePointer(1);
            Point minPoint = new Point();
            Point maxPoint = new Point();
            opencv_core.minMaxLoc(ret, minVal, maxVal, minPoint, maxPoint, null);
            result.setThreshold(threshold);
            result.setValue(maxVal.get());
            if (maxVal.get() < threshold) {
                log.info("没有匹配");
                result.setMatch(false);
                return result;
            } else {
                log.info("mat", "结果匹配");
                result.setMatch(true);
            }
            result.getPoint().put(new Point(left + maxPoint.x(), top + maxPoint.y()));
            result.getRect().put(new Rect(left + maxPoint.x(), top + maxPoint.y(), template1.size().width(), template1.size().height()));
            int x = Math.round(left + maxPoint.x() + template1.size().width() / 2);
            int y = Math.round(top + maxPoint.y() + template1.size().height() / 2);
            result.getCenter().put(new Point(x, y));
            log.info(StrUtil.format("{},{}", minVal.get(), maxVal.get()));
            log.info(StrUtil.format("min=({},{}),max=({},{})", minPoint.x(), minPoint.y(), maxPoint.x(), maxPoint.y()));
            return result;
        } catch (Exception e) {
            return result;
        }
    }


    public static Mat cutMat(Mat source, int left, int top, int right, int bottom) {
        int width = right - left + 1;
        int height = bottom - top + 1;
        Mat tmp = new Mat(source, new Rect(left, top, width, height));
        Mat sub = new Mat(tmp.size(), tmp.type());
        tmp.copyTo(sub);
        return sub;
    }

    public static Mat cutMat(Mat source, Rect rect) {
        Mat tmp = new Mat(source, rect);
        Mat sub = new Mat(tmp.size(), tmp.type());
        tmp.copyTo(sub);
        return sub;
    }

    public static int getColorValue(byte x) {
        return x < 0 ? 255 + x : x;
    }

    public static Scalar getScalar(Mat mat, int x, int y) {
        BytePointer bp = mat.ptr(y, x);
        byte[] bgr = new byte[3];
        bp.get(bgr);
        int r = getColorValue(bgr[0]);
        int b = getColorValue(bgr[1]);
        int g = getColorValue(bgr[2]);
        return new Scalar(b, g, r, 0);
    }

    /**
     * 求颜色近似度
     */
    public static double scalarDist(Scalar c1, Scalar c2) {
        double rmean = (c1.red() + c2.red()) / 2;
        double r = c1.red() - c2.red();
        double g = c1.green() - c2.green();
        double b = c1.blue() - c2.blue();
        return Math.sqrt((2 + rmean / 256) * Math.pow(r, 2) + 4 * Math.pow(g, 2) + (2 + (255 - rmean) / 256) * Math.pow(b, 2));
    }

    /**
     * 在画面位置进行十字标记
     */
    public static void crossMark(Mat mat, Point point) {
        opencv_imgproc.line(mat, new Point(point.x() - 10, point.y()),
                new Point(point.x() + 10, point.y()),
                new Scalar(255, 255, 255, 0));
        //
        opencv_imgproc.line(mat, new Point(point.x(), point.y() - 10),
                new Point(point.x(), point.y() + 10),
                new Scalar(255, 255, 255, 0));
    }

    /**
     * 画矩形
     */
    public static void rect(Mat mat, Rect rect) {
        opencv_imgproc.rectangle(mat, rect, new Scalar(0, 0, 255, 0), 4, opencv_imgproc.FILLED, 0);
    }

    public static BufferedImage capScreen() throws Exception {
        Robot robot = new Robot();
        Rectangle size = new Rectangle(Toolkit.getDefaultToolkit()
                .getScreenSize());
        BufferedImage buf = robot.createScreenCapture(size);
        return buf;
    }

    public static Mat bufferedImageToMat(BufferedImage bufferedImage) {
        OpenCVFrameConverter.ToMat openCVConverter = new OpenCVFrameConverter.ToMat();
        Java2DFrameConverter java2DConverter = new Java2DFrameConverter();
        Frame frame = java2DConverter.convert(bufferedImage);
        return openCVConverter.convert(frame);
    }

    public static BufferedImage matToBufferedImage(Mat mat) {
        OpenCVFrameConverter.ToMat openCVConverter = new OpenCVFrameConverter.ToMat();
        Frame frame = openCVConverter.convert(mat);
        OpenCVFrameConverter.ToIplImage converter = new OpenCVFrameConverter.ToIplImage();
        IplImage image = converter.convert(frame);
        BufferedImage bufferedImage = new BufferedImage(image.width(),
                image.height(), BufferedImage.TYPE_3BYTE_BGR);
        WritableRaster raster = bufferedImage.getRaster();
        DataBufferByte dataBuffer = (DataBufferByte) raster.getDataBuffer();
        byte[] data = dataBuffer.getData();
        ((ByteBuffer) image.createBuffer()).get(data);
        return bufferedImage;
    }
}